﻿using System;
using System.Collections.Generic;
using MugenInjection.Interface;

namespace MugenInjection.Scope
{
    /// <summary>
    ///     Represents the managed scope lifecycle.
    /// </summary>
    public class ManagedScopeLifecycle : ScopeLifecycleBase
    {
        #region Static members

        private static IManagedScope _activeManagedScope;

        /// <summary>
        ///     Gets the object to lock scope-operations.
        /// </summary>
        public static readonly object ScopeLocker = new object();

        /// <summary>
        ///     Sets the current managet scope, if you use this method use <see cref="ScopeLocker" /> for thread-safety.
        /// </summary>
        /// <param name="scope">
        ///     The specified <see cref="IManagedScope" />, if any.
        /// </param>
        public static void SetManagedScope(IManagedScope scope)
        {
            if (scope != null && scope.IsDisposed)
                throw new ObjectDisposedException("The managed scope is disposed.");
            _activeManagedScope = scope;
        }

        /// <summary>
        ///     Gets the current managet scope, if you use this method use <see cref="ScopeLocker" /> for thread-safety.
        /// </summary>
        /// <returns>
        ///     An instance of <see cref="IManagedScope" />.
        /// </returns>
        public static IManagedScope GetManagedScope(bool throwErrorIfNull = true)
        {
            if (throwErrorIfNull && _activeManagedScope == null)
                throw new InvalidOperationException(
                    "The currently active IManagedScope was not found. To create a new scope, use the method CreateManagedScope.");
            return _activeManagedScope;
        }

        #endregion

        #region Fields

        private readonly Dictionary<IManagedScope, object> _cache = new Dictionary<IManagedScope, object>();

        #endregion

        #region Overrides of ScopeLifecycleBase

        /// <summary>
        ///     Resolves instance for the specified <see cref="IBindingContext" /> from scope.
        /// </summary>
        /// <returns>An instance of the service.</returns>
        protected override object GetObjectFromScope()
        {
            IManagedScope currentScope;
            lock (ScopeLocker)
                currentScope = GetManagedScope();
            lock (Locker)
            {
                object result;
                if (!_cache.TryGetValue(currentScope, out result))
                {
                    currentScope.Disposed += ManagedScopeOnDisposed;
                    result = ResolveCurrentService();
                    _cache[currentScope] = result;
                }
                return result;
            }
        }

        /// <summary>
        ///     Gets all created objects in the current scope.
        /// </summary>
        protected override IList<object> GetObjectsFromScope()
        {
            var objects = new object[_cache.Values.Count];
            _cache.Values.CopyTo(objects, 0);
            return objects;
        }

        /// <summary>
        ///     Clears the current scope.
        /// </summary>
        protected override void ClearScope()
        {
            foreach (var valuePair in _cache)
                valuePair.Key.Disposed -= ManagedScopeOnDisposed;
            _cache.Clear();
        }

        #endregion

        #region Events

        private void ManagedScopeOnDisposed(IDisposableObject disposableObject)
        {
            disposableObject.Disposed -= ManagedScopeOnDisposed;
            var managedScope = disposableObject as IManagedScope;
            if (managedScope == null) return;
            lock (Locker)
            {
                object obj;
                if (_cache.TryGetValue(managedScope, out obj))
                {
                    TryReleaseObjectWithNotification(obj);
                    _cache.Remove(managedScope);
                }
            }
        }

        #endregion
    }
}